using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Speedball2 {
  public class Player : Entity {
    public int _index;

    public int _distanceToBall;
    public Point _targetXY;
    public Point _launchXY;
    public Point _zoneCenterXY;
    public Point _zoneXY1;
    public Point _zoneXY2;
    public int _timeInPossession;
    public int _goalsScored;
    public int _successfulTackles;
    public int _goalsSaved;
    public int _substitutions;
    public int _random;
    public int _unkBonusFromSpd;
    public Attributes _attributes;

    public int _agrTemp;
    public int _attTemp;
    public int _defTemp;
    public int _spdTemp;
    public int _thrTemp;
    public int _powTemp;
    public int _staTemp;
    public int _intTemp;

    public int _equipmentSpriteIndex;
    public int _equipmentBackupAttribute;
    public int _equipmentBackupAttributeTemp;
    public int _field_5B_b;

    public int _pPersonAttributes;
    public int _personAttributesIndex;
    public AttributesTypeEnum _personAttributesType;
    public int [] _distanceToOpposingPlayers;

    public Player (byte [] bytes, int offset, int index, bool isTeam1)
      : base (bytes, offset) {
      _index = index;
      offset += Entity.Size;

      _distanceToBall = bytes.ReadWord (offset);
      offset += 2;
      _targetXY = new Point (bytes.ReadWord (offset), bytes.ReadWord (offset + 2));
      offset += 4;
      _launchXY = new Point (bytes.ReadWord (offset), bytes.ReadWord (offset + 2));
      offset += 4;
      _zoneCenterXY = new Point (bytes.ReadWord (offset), bytes.ReadWord (offset + 2));
      offset += 4;
      _zoneXY1 = new Point (bytes.ReadWord (offset + 0), bytes.ReadWord (offset + 4));
      _zoneXY2 = new Point (bytes.ReadWord (offset + 2), bytes.ReadWord (offset + 6));
      offset += 8;

      _timeInPossession = bytes.ReadByte (offset++);
      _goalsScored = bytes.ReadByte (offset++);
      _successfulTackles = bytes.ReadByte (offset++);
      _goalsSaved = bytes.ReadByte (offset++);
      _substitutions = bytes.ReadByte (offset++);
      _random = bytes.ReadByte (offset++);
      _unkBonusFromSpd = bytes.ReadByte (offset++);

      _attributes = new Attributes (bytes, offset);
      offset += Attributes.Size;

      _agrTemp = bytes.ReadByte (offset++);
      _attTemp = bytes.ReadByte (offset++);
      _defTemp = bytes.ReadByte (offset++);
      _spdTemp = bytes.ReadByte (offset++);
      _thrTemp = bytes.ReadByte (offset++);
      _powTemp = bytes.ReadByte (offset++);
      _staTemp = bytes.ReadByte (offset++);
      _intTemp = bytes.ReadByte (offset++);

      _equipmentSpriteIndex = bytes.ReadByte (offset++);
      _equipmentBackupAttribute = bytes.ReadByte (offset++);
      _equipmentBackupAttributeTemp = bytes.ReadByte (offset++);
      _field_5B_b = bytes.ReadByte (offset++);

      _pPersonAttributes = (int) bytes.ReadLong (offset);
      _personAttributesIndex = (_pPersonAttributes - Attributes.GetAttributesBaseAddress (_pPersonAttributes, out _personAttributesType)) / Attributes.Size;
      offset += 4;
      //if (isTeam1)
      //  _personAttributesType = AttributesTypeEnum.Team1;
      //else
      //  _personAttributesType = AttributesTypeEnum.Team2;

      _distanceToOpposingPlayers = new int [9];
      for (int i = 0; i < 9; i++) {
        _distanceToOpposingPlayers [i] = bytes.ReadWord (offset);
        offset += 2;
      }

      if ((offset - _offset) != Size)
        throw new Exception ();
    }

    public void InitAttributesAndStats (Match match) {
      Attributes attributes = match.GetPersonAttributes (_personAttributesIndex, _personAttributesType);
      _attributes._health = attributes._health;
      _attributes._initialDir = attributes._initialDir;
      _attributes._pos = attributes._pos;
      _attributes._agr = attributes._agr;
      _attributes._att = attributes._att;
      _attributes._def = attributes._def;
      _attributes._spd = attributes._spd;
      _attributes._thr = attributes._thr;
      _attributes._pow = attributes._pow;
      _attributes._sta = attributes._sta;
      _attributes._int = attributes._int;
      _equipmentSpriteIndex = 0;
      _timeInPossession = 0;
      _goalsScored = 0;
      _successfulTackles = 0;
      _goalsSaved = 0;
      _substitutions = 0;
    }

    public void InitZoneCenterXY () {
      _zoneCenterXY = new Point (((_zoneXY1.X + _zoneXY2.X) / 2) & 0xFFFE,
                            ((_zoneXY1.Y + _zoneXY2.Y) / 2) & 0xFFFE);
    }

    public void Think (Match match) {
      if (_index == 0) {
        this.ToString ();
      }
      sub_D2DE_SetInput (match);
      _timer--;
      if (_timer < 0)
        _timer = 0;
      if (_pThink == 0xEB7A)
        sub_EB7A (match);
      else if (_pThink == 0xEC0C)
        sub_EC0C (match);
      else if (_pThink == 0xED56)
        sub_ED56_TackleCheckHit (match);
      else if (_pThink == 0xF078)
        sub_F078 (match);
      else if (_pThink == 0xF212)
        sub_F212 (match);
      else if (_pThink == 0xF290)
        sub_F290 (match);
      else if (_pThink == 0xF2E2)
        sub_F2E2 (match);
      else if (_pThink == 0xF334)
        sub_F334 (match);
      else if (_pThink == 0xF33C)
        sub_F33C (match);
      else if (_pThink != 0xF344) // Rts
        throw new Exception ();
      sub_D448_PreventMoveOnPlayers (match);
      ExecuteOpcode (match);
      sub_F6B8 ();
      sub_FB6C (match);
    }

    public void sub_CEEA_InitGoalerAnim (Match match) {
      Player [] players = match.PlayersTeam1;
      if (!Bit3_IsTeam1_IsElectrobounced)
        players = match.PlayersTeam2;

      foreach (Player a5_player in players) {
        if (a5_player.Bit4_IsFalling)
          continue;
        if (a5_player.Bit7_IsInvisible)
          continue;
        int pOpcodesList = 0x6026;
        int velocity = 0;
        a5_player.Bit0_IsBusy = true;
        a5_player._opcodeIndex = 0;
        a5_player._dir &= 4;
        if (a5_player == this) {
          pOpcodesList += 32;
          velocity = 5;
          a5_player._dir = a5_player._attributes._initialDir ^ 4;
          if (match.EntityHoldingBall == null)
            match.EntityHoldingBall = a5_player;
        }
        a5_player.sub_F5A8_SetOpcodesAndVelocityFromDir (pOpcodesList, velocity);
        a5_player._pThink = 0xF344;
      }
    }

    private void sub_D2DE_SetInput (Match match) {
      //if (this == match.PlayerHavingInputTeam1)
      //  if (match.Bit7_IsClockPaused)
      //    if (Bit0_IsBusy) {
      //      int z = 0;
      //    }
      if (match.Bit7_IsClockPaused) {
        if (Bit0_IsBusy)
          return;
        if (_timer != 0)
          return;
        sub_F346_CalcBonusFromInt ();
        if (match.Leg == 2) {
          //Main.StepMode = StepModeEnum.StepByStep;
          int presentationIndex = 0;
          //if (_index == 6)
          //  if (!Bit3_IsTeam1_IsElectrobounced) {
          //    int z = 0;
          //  }
          if (_attributes._initialDir != 0)
            presentationIndex = 9;
          _targetXY = Player.GetPresentationXY (presentationIndex + _index);
        }
        else {
          _targetXY = _launchXY;
        }
        if (!Bit7_IsInvisible) {
          SetOnOffScreen (match);
          if (Bit6_IsOffscreen)
            if (!match.IsOnscreen (_targetXY))
              _terrainXY = _targetXY;
        }
        else {
          _terrainXY = _targetXY;
        }
        sub_EB7A (match);
      }
      else if (this == match.PlayerHavingInputTeam1) {
        sub_D520 (match);
        if ((!match.Bit0_IsComputerPlayer1) && (IsOnscreen_Margin16 (match.ScrollXY))) {
          sub_D724 (match, match._input1);
        }
        else {
          if (_timer != 0)
            return;
          sub_D742_AII (match);
        }
      }
      else if (this == match.PlayerHavingInputTeam2) {
        //if (_index == 6)
        //  if (!Bit3_IsTeam1_IsElectrobounced)
        //    if (_targetXY == new Point (0x1B1, 0x3DD)) {
        //      Main.StepMode = StepModeEnum.StepByStep;
        //    }
        sub_D520 (match);
        if ((!match.Bit1_IsComputerPlayer2) && (IsOnscreen_Margin16 (match.ScrollXY))) {
          sub_D724 (match, match._input2);
        }
        else {
          if (_timer != 0)
            return;
          sub_D742_AII (match);
        }
      }
      else {
        if (_timer != 0)
          return;
        //if (_index == 6)
        //  if (!Bit3_IsTeam1_IsElectrobounced)
        //    if (_targetXY == new Point (0x1B1, 0x3DD)) {
        //      //if (match._frame == 1992) {
        //      int z = 0;
        //    }
        //Console.WriteLine ("{0}: {1}", match._frame, _terrainXY);
        //Main.StepMode = StepModeEnum.StepByStep;
        //int z = 0;
        sub_E076 (match);
      }
    }




    private void sub_D448_PreventMoveOnPlayers (Match match) {
      if (match.Bit7_IsClockPaused)
        return;
      if (Bit6_IsOffscreen)
        return;
      Player [] players = match.PlayersTeam2;
      if (!Bit3_IsTeam1_IsElectrobounced)
        players = match.PlayersTeam1;
      foreach (Player player in players) {
        if (player.Bit4_IsFalling)
          continue;
        if (_distanceToOpposingPlayers [player._index] > 30)
          continue;
        if (player.Bit6_IsOffscreen)
          continue;
        sub_D4AC_PreventMoveOnPlayer (player);
      }
    }

    private void sub_D4AC_PreventMoveOnPlayer (Player player) {
      if (Bit4_IsFalling) {
        if (_opcodeIndex >= 18) {
          _velocityXY = Point.Empty;
          _opcodeIndex = 18;
        }
      }
      DirBits dirBits = GetDirBitsToTargetBetter (player._terrainXY);
      if (_velocityXY.Y > 0) {
        if (dirBits.Bit1_IsDown)
          _terrainXY.Y -= _velocityXY.Y;
      }
      else if (_velocityXY.Y < 0) {
        if (dirBits.Bit0_IsUp)
          _terrainXY.Y -= _velocityXY.Y;
      }
      if (_velocityXY.X > 0) {
        if (dirBits.Bit3_IsRight)
          _terrainXY.X -= _velocityXY.X;
      }
      else if (_velocityXY.X < 0) {
        if (dirBits.Bit2_IsLeft)
          _terrainXY.X -= _velocityXY.X;
      }
    }

    private void sub_D520 (Match match) {
      if (match.EntityHoldingBall != null)
        return;
      if (Bit4_IsFalling)
        return;
      if (Bit2_IsThrowing)
        return;
      if (Bit7_IsInvisible)
        return;
      if (_distanceToBall > 16)
        return;
      if (match.Bit2_IsBallInScoreMultiplier)
        return;
      if (_pThink == 0xF334) {
        sub_D672 (match);
        return;
      }
      Entity entityBall = match.EntityBall;
      if ((entityBall._spriteIndex > 2) && (!Bit1_IsJumping))
        return;
      if (entityBall.Bit3_IsTeam1_IsElectrobounced)
        if (match._electrobouncedBallRemainingHits != 0)
          if (sub_FC24 (match))
            if (_attributes._initialDir != match.PlayerThrower._attributes._initialDir) {
              sub_D632_HitByElectrobouncedBall (match);
              return;
            }
      if (Bit1_IsJumping)
        entityBall._pOpcodes = 0x554C;
      else {
        entityBall._pOpcodes = 0x5588;
        if (!Bit0_IsBusy)
          if (_velocityXY == Point.Empty)
            if (entityBall._velocityXY != Point.Empty)
              sub_F1B4 (match);
      }
      entityBall._opcodeIndex = _opcodeIndex;
      match.EntityHoldingBall = this;
      if (entityBall._velocityXY == Point.Empty)
        return;
      if (_attributes._initialDir == match.PlayerThrower._attributes._initialDir)
        return;
      _goalsSaved++;
      entityBall.Bit3_IsTeam1_IsElectrobounced = false;
    }

    private void sub_D632_HitByElectrobouncedBall (Match match) {
      match._electrobouncedBallRemainingHits--;
      _dir = match.EntityBall._dir;
      sub_EEFC_HitByTackle (match, match.PlayerThrower);
      _opcodeIndex = 0;
      Bit0_IsBusy = true;
      Bit4_IsFalling = true;
      _pThink = 0xF344;
      sub_F5A8_SetOpcodesAndVelocityFromDir (0x5EE6, 3);
    }

    private void sub_D672 (Match match) {
      Entity ball = match.EntityBall;
      if (ball._spriteIndex > 2)
        return;
      if ((ball.Bit3_IsTeam1_IsElectrobounced) &&
          (match._electrobouncedBallRemainingHits != 0) &&
          (match.PlayerThrower._attributes._initialDir != _attributes._initialDir)) {
        sub_D632_HitByElectrobouncedBall (match);
        return;
      }
      if (match.PlayerThrower._attributes._initialDir != _attributes._initialDir) {
        _goalsSaved++;
        ball.Bit3_IsTeam1_IsElectrobounced = false;
      }
      ball._dir = Match.GetDirBallBlockedByGoal (_dir, _attributes._initialDir);
      match.SetBallSpeedFromThr (((_attributes._thr / 2) | (_attributes._thr)) / 2);
      ball._velocityXY = Match.GetVelocityXYFromDir (ball._dir, 8);
      ball._pOpcodes = 0x54C6;
      ball._opcodeIndex = 0;
    }

    private void sub_D724 (Match match, Input input) {
      _timer = 1;
      if (!Bit0_IsBusy) {
        _pThink = 0xF344;
        sub_F47E (match, input);
      }
    }

    private void sub_D742_AII (Match match) {
      if (Bit0_IsBusy)
        return;
      _random = Game.myRandom.NextByte ();
      sub_F346_CalcBonusFromInt ();
      if (this != match.EntityHoldingBall)
        if (sub_E854 (match))
          return;
      Entity a3_entity = match.PlayerHavingInputTeam1;
      if (this == a3_entity)
        a3_entity = match.PlayerHavingInputTeam2;
      if (this != match.EntityHoldingBall) {
        // A
        if (_attributes._pos == 0) {
          sub_E61A_GoalUnk (match);
          return;
        }
        Entity a0_entity = this;
        if (!a0_entity.IsOnscreen_Margin16 (match.ScrollXY))
          a3_entity = match.EntityBall;
        else {
          // B
          Point p2;
          bool d0 = sub_D97E_AII_Items (match, new DirBits (), new DirBits (), out a0_entity);
          if ((d0) &&
              (match.EntityBall.CalcVector (_targetXY, out p2) <= _distanceToBall))
            a3_entity = a0_entity;
          else { // D7CE, Else3
            if (a3_entity != match.EntityHoldingBall)
              if (!match.Bit2_IsBallInScoreMultiplier)
                if ((a3_entity.Bit4_IsFalling) ||
                    ((_attributes._agr / 2) <= _random) ||
                    (_distanceToBall <= a3_entity.GetPlayer._distanceToBall))
                  a3_entity = match.EntityBall;
          }
          if ((_attributes._agr / 2) > _random) {
            sub_EA56 (match, a3_entity);
            return;
          }
        }
        // D81C, Else2
        //a0_entity = a3_entity;
        _targetXY = sub_F364 (a3_entity);
        sub_EB7A (match);
      }
      else { // D832, Else1
        DirBits dirBits_d6A, dirBits_d6B;
        sub_D902 (match, a3_entity.GetPlayer, out dirBits_d6A, out dirBits_d6B);
        int type = -1;
        if (_attributes._pos == 0)
          type = 2;
        else {
          if (sub_DCCC (match, dirBits_d6A, dirBits_d6B))
            return;
          if ((!a3_entity.Bit4_IsFalling) && (a3_entity.GetPlayer._distanceToBall <= 64))
            type = 2;
          else {
            // C
            int d0 = _attributes._int;
            if (_attributes._pos > 2)
              d0 /= 2;
            Entity notUsed;
            if (d0 > _random)
              if (sub_D97E_AII_Items (match, dirBits_d6A, dirBits_d6B, out notUsed))
                type = 1;
            if (type == -1) {
              if (_attributes._initialDir == 0) {
                if (_terrainXY.Y <= _zoneCenterXY.Y)
                  type = 2;
                else
                  _targetXY.Y = (_terrainXY.Y + _zoneXY1.Y) / 2;
              }
              else {
                if (_terrainXY.Y >= _zoneCenterXY.Y)
                  type = 2;
                else
                  _targetXY.Y = (_terrainXY.Y + _zoneXY2.Y) / 2;
              }
              if (type == -1) {
                // D
                if (((_random >> 3) & 1) == 1) {
                  DirBits dirBits_d0 = sub_DA86 (dirBits_d6A, dirBits_d6B);
                  if ((dirBits_d6A.Data == dirBits_d0.Data) ||
                      (dirBits_d6B.Data == dirBits_d0.Data))
                    type = 2;
                  else
                    type = 1;
                }
                else {
                  DirBits dirBits_d0 = sub_DB32 (dirBits_d6A, dirBits_d6B);
                  if ((dirBits_d6A.Data == dirBits_d0.Data) ||
                      (dirBits_d6B.Data == dirBits_d0.Data))
                    type = 2;
                  else
                    type = 1;
                }
              }
            }
          }
        }
        if (type == 1)
          sub_EB7A (match);
        else if (type == 2)
          if (_attributes._pos < 3)
            sub_DBE8 (match, dirBits_d6A, dirBits_d6B);
          else
            sub_DF2E (match, a3_entity.GetPlayer);
        else
          throw new Exception ();
      }
    }

    private void sub_D902 (Match match, Player a3_player, out DirBits dirBits1, out DirBits dirBits2) {
      Player [] players = match.PlayersTeam1;
      if (Bit3_IsTeam1_IsElectrobounced)
        players = match.PlayersTeam2;
      if (!a3_player.Bit4_IsFalling) {
        Point targetXY = sub_F364 (a3_player);
        dirBits1 = GetDirBitsToTargetBetter (targetXY);
      }
      else
        dirBits1 = new DirBits ();
      int d0 = sub_DE7E_CalcDistanceUnkFromInt ();
      Player playerFound = null;
      foreach (Player player in players) {
        if (d0 < player._distanceToBall)
          continue;
        if (player == a3_player)
          continue;
        if (player.Bit4_IsFalling)
          continue;
        playerFound = player;
        d0 = player._distanceToBall;
      }
      if (playerFound != null) {
        Point targetXY = sub_F364 (playerFound);
        dirBits2 = GetDirBitsToTargetBetter (targetXY);
      }
      else
        dirBits2 = new DirBits ();
      match._dirBitsA = dirBits1;
      match._dirBitsB = dirBits2;
    }

    private bool sub_D97E_AII_Items (Match match, DirBits dirBits1, DirBits dirBits2, out Entity entity) {
      entity = match.EntityItemToken1;
      if (sub_D9DC_AII (match, dirBits1, dirBits2, match.EntityItemToken1))
        return (true);
      entity = match.EntityItemToken2;
      if (sub_D9DC_AII (match, dirBits1, dirBits2, match.EntityItemToken2))
        return (true);
      entity = match.EntityItemEquipment;
      if (sub_D9DC_AII (match, dirBits1, dirBits2, match.EntityItemEquipment))
        return (true);
      entity = match.EntityItemCoins1;
      if (sub_D9DC_AII (match, dirBits1, dirBits2, match.EntityItemCoins1))
        return (true);
      entity = match.EntityItemCoins2;
      if (sub_D9DC_AII (match, dirBits1, dirBits2, match.EntityItemCoins2))
        return (true);
      entity = match.EntityItemCoins3;
      if (sub_D9DC_AII (match, dirBits1, dirBits2, match.EntityItemCoins3))
        return (true);
      entity = match.EntityItemCoins4;
      if (sub_D9DC_AII (match, dirBits1, dirBits2, match.EntityItemCoins4))
        return (true);
      return (false);
    }

    private bool sub_D9DC_AII (Match match, DirBits dirBits1, DirBits dirBits2, Entity item) {
      item.SetOnOffScreen (match);
      if (item.Bit6_IsOffscreen)
        return (false);
      if (item.Bit7_IsInvisible)
        return (false);
      DirBits dirBits = GetDirBitsToTargetBetter (item._terrainXY);
      if (dirBits1.Data == dirBits.Data)
        return (false);
      if (dirBits2.Data == dirBits.Data)
        return (false);
      _targetXY = item._terrainXY;
      return (true);
    }

    private DirBits sub_DA20 () {
      Point xy = _terrainXY;
      sub_E41A (ref xy);
      if (xy.X <= 96)
        xy.X = 56;
      else if (xy.X >= 544)
        xy.X = 584;
      sub_E41A (ref xy);
      _targetXY.X = xy.X;
      xy.Y = _targetXY.Y;
      return (GetDirBitsToTargetBetter (xy));
    }

    private DirBits sub_DA56 () {
      Point xy = Point.Empty;
      xy.X = (_terrainXY.X + _zoneXY1.X) / 2;
      _targetXY.X = xy.X;
      xy.Y = _targetXY.Y;
      return (GetDirBitsToTargetBetter (xy));
    }

    private DirBits sub_DA6E () {
      Point xy = Point.Empty;
      xy.X = (_terrainXY.X + _zoneXY2.X) / 2;
      _targetXY.X = xy.X;
      xy.Y = _targetXY.Y;
      return (GetDirBitsToTargetBetter (xy));
    }

    private DirBits sub_DA86 (DirBits dirBits1, DirBits dirBits2) {
      DirBits dirBits = sub_DA20 ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      dirBits = sub_DA56 ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      dirBits = sub_DA6E ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      // A 
      _targetXY.Y = (_terrainXY.Y + _zoneCenterXY.Y) / 2;
      dirBits = sub_DA56 ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      dirBits = sub_DA6E ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      // B
      if (_attributes._initialDir == 0)
        _targetXY.Y = (_terrainXY.Y + _zoneXY2.Y) / 2;
      else
        _targetXY.Y = (_terrainXY.Y + _zoneXY1.Y) / 2;
      dirBits = sub_DA56 ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      return (sub_DA6E ());
    }

    private DirBits sub_DB32 (DirBits dirBits1, DirBits dirBits2) {
      DirBits dirBits = sub_DA20 ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      dirBits = sub_DA6E ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      dirBits = sub_DA56 ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      // A 
      _targetXY.Y = (_terrainXY.Y + _zoneCenterXY.Y) / 2;
      dirBits = sub_DA6E ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      dirBits = sub_DA56 ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      // B
      if (_attributes._initialDir == 0)
        _targetXY.Y = (_terrainXY.Y + _zoneXY2.Y) / 2;
      else
        _targetXY.Y = (_terrainXY.Y + _zoneXY1.Y) / 2;
      dirBits = sub_DA6E ();
      if ((dirBits1.Data != dirBits.Data) &&
          (dirBits2.Data != dirBits.Data))
        return (dirBits);
      return (sub_DA56 ());
    }

    private void sub_DBE8 (Match match, DirBits dirBits1, DirBits dirBits2) {
      if (match.EntityBall.Bit3_IsTeam1_IsElectrobounced) {
        dirBits1.Reset ();
        dirBits2.Reset ();
      }
      int distance = sub_DE7E_CalcDistanceUnkFromInt ();
      Player playerFound = null;
      int pos = _attributes._pos;
      if (_attributes._pos == 0)
        pos = 2;
      else
        pos++;
      for (; ; ) {
        Player [] players = match.PlayersTeam1;
        if (!Bit3_IsTeam1_IsElectrobounced)
          players = match.PlayersTeam2;
        foreach (Player a5_player in players) {
          if (a5_player.Bit4_IsFalling)
            continue;
          if (pos > a5_player._attributes._pos)
            continue;
          if (this == a5_player)
            continue;
          if (!a5_player.sub_E05C (this, dirBits1, dirBits2))
            continue;
          if (distance < a5_player._distanceToBall)
            continue;
          distance = a5_player._distanceToBall;
          playerFound = a5_player;
        }
        if ((playerFound != null) ||
            (pos < _attributes._pos))
          break;
        pos--;
        if (pos == 0)
          break;
      }
      if (playerFound != null) {
        _targetXY = sub_F364 (playerFound);
        if (((_attributes._pos != 0) || (playerFound._attributes._pos == 1)) &&
            ((_attributes._thr * 2) > playerFound._distanceToBall))
          sub_EFFC (match);
        else
          sub_EFD8 (match);
      }
      else
        sub_EF7C (match);
    }

    private bool sub_DCCC (Match match, DirBits dirBits1, DirBits dirBits2) {
      int scoreMultiplier;
      if (Bit3_IsTeam1_IsElectrobounced)
        scoreMultiplier = match.ScoreMultipliers._team1;
      else
        scoreMultiplier = match.ScoreMultipliers._team2;
      if (scoreMultiplier != 2) {
        if ((((_terrainXY.X >= 48) && (_terrainXY.X <= 64)) &&
             ((_terrainXY.Y > 640) || (_terrainXY.Y < 576)))
        ||
            (((_terrainXY.X >= 576) && (_terrainXY.X <= 592)) &&
             ((_terrainXY.Y > 576) || (_terrainXY.Y < 512)))) {
          _targetXY.X = _terrainXY.X;
          _targetXY.Y = 576;
          DirBits dirBits = GetDirBitsToTargetBetter (_targetXY);
          if ((match.EntityBall.Bit3_IsTeam1_IsElectrobounced) ||
              ((dirBits.Data != dirBits1.Data) &&
               (dirBits.Data != dirBits2.Data))) {
            sub_EFFC (match);
            return (true);
          }
        }
      }
      // Else1
      if ((_terrainXY.X <= 213) || (_terrainXY.X > 426)) {
        if (_terrainXY.X <= 213) {
          _targetXY = new Point (32, 464);
        }
        else if (_terrainXY.X > 426) {
          _targetXY = new Point (608, 688);
        }
        DirBits dirBits = GetDirBitsToTargetBetter (_targetXY);
        if ((match.EntityBall.Bit3_IsTeam1_IsElectrobounced) ||
            ((dirBits.Data != dirBits1.Data) &&
             (dirBits.Data != dirBits2.Data))) {
          int dir = GetDirFromDirBits (dirBits);
          if (((_attributes._initialDir == 0) && ((dir == 7) || (dir == 1))) ||
              ((_attributes._initialDir != 0) && ((dir == 3) || (dir == 5)))) {
            sub_EFD8 (match);
            return (true);
          }
        }
      }
      // Else2
      if ((!match.EntityBall.Bit3_IsTeam1_IsElectrobounced) &&
          ((_attributes._agr / 2) > _random)) {
        if ((sub_DE30 (match, match.Electrobounces.EntityLeft, dirBits1, dirBits2)) ||
            (sub_DE30 (match, match.Electrobounces.EntityRight, dirBits1, dirBits2)))
          return (true);
      }
      return (false);
    }

    private bool sub_DE30 (Match match, Entity electrobounce, DirBits dirBits1, DirBits dirBits2) {
      if (UnkInRect (electrobounce._terrainXY, 106) > 106)
        return (false);
      _targetXY = electrobounce._terrainXY;
      DirBits dirBits = GetDirBitsToTargetBetter (_targetXY);
      if ((dirBits.Data == dirBits1.Data) ||
          (dirBits.Data == dirBits2.Data))
        return (false);
      int dir = GetDirFromDirBits (dirBits);
      if ((dir == 0) || (dir == 4))
        return (false);
      sub_EFFC (match);
      return (true);
    }

    public int sub_DE7E_CalcDistanceUnkFromInt () {
      return (_attributes._int * 2);
    }

    public int sub_DE88_CalcBonusFromSpd () {
      int index = (_attributes._spd - 100) / 10;
      int offset = 0x562 + index;
      return (Game.AtariDisk.ReadByte (Game.AtariRamToDisk (offset)));
    }

    private Point sub_DEA4_SetInputUnk (Match match) {
      Input input;
      if (Bit3_IsTeam1_IsElectrobounced)
        input = match._input1;
      else
        input = match._input2;
      int x = 320;
      if (x < _terrainXY.X) {
        x -= 16;
        input.Bit2_IsLeft = true;
      }
      else if (x > _terrainXY.X) {
        x += 16;
        input.Bit3_IsRight = true;
      }
      else {
        if (((_random >> 6) & 1) == 0) {
          x = 272;
          input.Bit2_IsLeft = true;
        }
        else {
          x = 368;
          input.Bit3_IsRight = true;
        }
      }
      int y;
      if (_attributes._initialDir == 0)
        y = 0;
      else
        y = 1152;
      if ((_attributes._int / 2) <= _random) {
        input.Bit2_IsLeft = false;
        input.Bit3_IsRight = false;
      }
      return (new Point (x, y));
    }

    private void sub_DF2E (Match match, Player player) {
      if (!match.EntityBall.Bit3_IsTeam1_IsElectrobounced) {
        Point targetXY = sub_DEA4_SetInputUnk (match);
        DirBits dirBits = GetDirBitsToTargetBetter (targetXY);
        if ((dirBits.Data == match._dirBitsA.Data) ||
            ((!dirBits.Bit0_IsUp) && (!dirBits.Bit1_IsDown))) {
          if ((player.Bit4_IsFalling) || (player._distanceToBall > 64)) {
            Point targetXY2 = _terrainXY;
            sub_E382 (this, ref targetXY2);
            _targetXY = targetXY2;
            DirBits dirBits2 = GetDirBitsToTargetBetter (_targetXY);
            if (dirBits2.Data != 0) {
              if ((dirBits2.Data != match._dirBitsA.Data) &&
                  (dirBits2.Data != match._dirBitsB.Data)) {
                sub_EB7A (match);
                return;
              }
            }
          }
          // Else2
          int distance = sub_DE7E_CalcDistanceUnkFromInt ();
          Player playerFound = null;
          for (int d5 = 3; ; ) {
            Player [] players = match.PlayersTeam1;
            if (!Bit3_IsTeam1_IsElectrobounced)
              players = match.PlayersTeam2;
            foreach (Player player2 in players) {
              if (d5 > player2._attributes._pos)
                continue;
              if (player2.Bit4_IsFalling)
                continue;
              if (player2 == this)
                continue;
              if (!player2.sub_E05C (this, match._dirBitsA, match._dirBitsB))
                continue;
              if (distance < player2._distanceToBall)
                continue;
              distance = player2._distanceToBall;
              playerFound = player2;
            }
            if (playerFound != null)
              break;
            if (d5 == 2)
              break;
            d5 = 2;
          }
          if (playerFound != null) {
            _targetXY = sub_F364 (playerFound);
            if ((_attributes._thr * 2) > playerFound._distanceToBall)
              sub_EFFC (match);
            else
              sub_EFD8 (match);
            return;
          }
        }
      }
      // Else1
      sub_EF50 (match);
    }

    private bool sub_E05C (Player player, DirBits dirBits1, DirBits dirBits2) {
      Point targetXY = player.sub_F364 (this);
      DirBits ditBits = player.GetDirBitsToTargetBetter (targetXY);
      if ((ditBits.Data == dirBits1.Data) ||
          (ditBits.Data == dirBits2.Data))
        return (false);
      else
        return (true);
    }

    private void sub_E076 (Match match) {
      if (Bit0_IsBusy)
        return;
      _random = Game.myRandom.NextByte ();
      sub_F346_CalcBonusFromInt ();
      if (sub_E854 (match))
        return;
      if (_attributes._pos == 0) {
        sub_E456 (match);
        return;
      }

      Point targetXY = Point.Empty;
      if (!Bit6_IsOffscreen) {
        if ((_attributes._agr / 2) > _random) {
          Player player = sub_E188 (match);
          if (player != null) {
            targetXY = sub_F364 (player);
          }
        }
      }
      // Else1
      if (targetXY.IsEmpty) {
        Player player = match.PlayerHavingInputTeam1;
        if (!Bit3_IsTeam1_IsElectrobounced)
          player = match.PlayerHavingInputTeam2;
        // A
        Entity entityBall = match.EntityBall;
        Point temp = new Point (entityBall._terrainXY.X + entityBall._velocityXY.X,
                                entityBall._terrainXY.Y + entityBall._velocityXY.Y);
        if (player.Bit4_IsFalling) {
          if (player._spriteIndex == 94) {
            if ((temp.Y >= _zoneXY1.Y) && (temp.Y <= _zoneXY2.Y))
              targetXY = temp;
          }
        }
        // B
        if (targetXY.IsEmpty) {
          temp = new Point (player._terrainXY.X + player._velocityXY.X,
                            player._terrainXY.Y + player._velocityXY.Y);
          if ((player._attributes._pos >= 3) && (_attributes._pos >= 3)) {
            targetXY = temp;
            sub_E382 (player, ref targetXY);
          }
        }
        // C
        if (targetXY.IsEmpty) {
          targetXY = temp;
          if (_attributes._pos == 1) {
            sub_E218 (match, ref player, ref targetXY);
            sub_E2B8 (player, ref targetXY);
          }
          else {
            if ((targetXY.X > _zoneXY1.X) && (targetXY.X < _zoneXY2.X)) {
              sub_E246 (player, ref targetXY);
            }
            else
              sub_E2B8 (player, ref targetXY);
          }
        }
      }
      if (targetXY.IsEmpty)
        throw new Exception ();
      _targetXY = targetXY;
      sub_EB7A (match);
    }

    private Player sub_E188 (Match match) {
      Player [] players = match.PlayersTeam1;
      if (Bit3_IsTeam1_IsElectrobounced)
        players = match.PlayersTeam2;
      int distance = sub_DE7E_CalcDistanceUnkFromInt ();
      Player playerFound = null;
      foreach (Player player in players) {
        if (player.Bit4_IsFalling)
          continue;
        if (player.Bit6_IsOffscreen)
          continue;
        Point targetXY = sub_F364 (player);
        if (!sub_E1F2 (targetXY))
          continue;
        if (distance <= _distanceToOpposingPlayers [player._index])
          continue;
        playerFound = player;
        distance = _distanceToOpposingPlayers [player._index];
      }
      return (playerFound);
    }

    private bool sub_E1F2 (Point targetXY) {
      if ((targetXY.X >= _zoneXY1.X) && (targetXY.X <= _zoneXY2.X))
        if ((targetXY.Y >= _zoneXY1.Y) && (targetXY.Y <= _zoneXY2.Y))
          return (true);
      return (false);

    }

    private void sub_E218 (Match match, ref Player player, ref Point targetXY) {
      if (player._attributes._pos < 2)
        return;
      player = match.PlayersTeam1 [0];
      if (!Bit3_IsTeam1_IsElectrobounced)
        player = match.PlayersTeam2 [0];
      targetXY.X = player._terrainXY.X + player._velocityXY.X;
      targetXY.Y = player._terrainXY.Y + player._velocityXY.Y;
    }

    private void sub_E246 (Player player, ref Point targetXY) {
      int pos = player._attributes._pos;
      if (pos >= 4)
        pos = 3;
      if (pos > _attributes._pos) {
        if (_attributes._initialDir == 0)
          if (targetXY.Y >= _zoneCenterXY.Y)
            targetXY.Y += _zoneXY2.Y;
          else
            targetXY.Y += _zoneCenterXY.Y;
        else {
          if (targetXY.Y <= _zoneCenterXY.Y)
            targetXY.Y += _zoneXY1.Y;
          else
            targetXY.Y += _zoneCenterXY.Y;
        }
      }
      else {
        if (_attributes._initialDir == 0)
          if (targetXY.Y <= _zoneCenterXY.Y)
            targetXY.Y += _zoneXY1.Y;
          else
            targetXY.Y += _zoneCenterXY.Y;
        else {
          if (targetXY.Y >= _zoneCenterXY.Y)
            targetXY.Y += _zoneXY2.Y;
          else
            targetXY.Y += _zoneCenterXY.Y;
        }
      }
      targetXY.Y /= 2;
      sub_E438 (ref targetXY);
    }

    private void sub_E2B8 (Player player, ref Point targetXY) {
      sub_E338 (ref targetXY);
      int x = Math.Abs (targetXY.X - player._terrainXY.X);
      int pos = player._attributes._pos;
      if (pos >= 4)
        pos = 3;
      int type;
      if (pos <= _attributes._pos) {
        if (_attributes._initialDir == 0)
          if (targetXY.Y >= _zoneCenterXY.Y) {
            x = -x;
            type = 0;
          }
          else
            type = 1;
        else {
          if (targetXY.Y <= _zoneCenterXY.Y)
            type = 0;
          else
            type = 1;
        }
      }
      else {
        if (_attributes._initialDir == 0)
          if (targetXY.Y <= _zoneCenterXY.Y)
            type = 0;
          else
            type = 1;
        else {
          if (targetXY.Y >= _zoneCenterXY.Y) {
            x = -x;
            type = 0;
          }
          else
            type = 1;
        }
      }
      if (type == 0) {
        targetXY.Y += x;
        sub_E344 (ref targetXY);
      }
      else {
        targetXY.Y = player._terrainXY.Y + player._velocityXY.Y;
        sub_E438 (ref targetXY);
      }
    }

    private void sub_E338 (ref Point targetXY) {
      targetXY.X = (targetXY.X + _zoneCenterXY.X) / 2;
      sub_E41A (ref targetXY);
    }

    private void sub_E344 (ref Point targetXY) {
      int d0;
      if (targetXY.Y < _zoneXY1.Y)
        d0 = _zoneXY1.Y;
      else {
        if (targetXY.Y > _zoneXY2.Y)
          d0 = _zoneXY2.Y;
        else
          return;
      }
      int temp = targetXY.Y;
      targetXY.Y = d0;
      d0 = temp;
      d0 = Math.Abs (d0 - targetXY.Y);
      if (targetXY.X <= _zoneCenterXY.X)
        targetXY.X += d0;
      else
        targetXY.X -= d0;
      sub_E41A (ref targetXY);
    }

    private void sub_E382 (Player player, ref Point targetXY) {
      if (targetXY.X >= 320)
        targetXY.X = 639 - targetXY.X;
      if (_attributes._initialDir != 0)
        targetXY.Y = 1151 - targetXY.Y;
      if (targetXY.Y >= 208) {
        targetXY.X = player._terrainXY.X + player._velocityXY.X;
        targetXY.Y = player._terrainXY.Y + player._velocityXY.Y;
        sub_E2B8 (player, ref targetXY);
      }
      else {
        int index1 = targetXY.X;
        int index2 = targetXY.Y;
        index1 -= 32;
        index2 -= 32;
        index1 /= 32;
        index2 /= 32;
        index1 *= 4;
        index2 *= 36;
        int offset = 0x2EE4;
        if (_attributes._pos != 4)
          offset = 0x2E0C;
        offset += index1 + index2;
        targetXY.X = Game.AtariDisk.ReadWord (Game.AtariRamToDisk (offset));
        targetXY.Y = Game.AtariDisk.ReadWord (Game.AtariRamToDisk (offset + 2));
        if (player._terrainXY.X >= 320)
          targetXY.X = 639 - targetXY.X;
        if (_attributes._initialDir != 0)
          targetXY.Y = 1151 - targetXY.Y;
        sub_E41A (ref targetXY);
      }
    }

    private void sub_E41A (ref Point targetXY) {
      if (targetXY.X < _zoneXY1.X)
        targetXY.X = _zoneXY1.X;
      else if (targetXY.X > _zoneXY2.X)
        targetXY.X = _zoneXY2.X;
    }

    private void sub_E438 (ref Point targetXY) {
      if (targetXY.Y < _zoneXY1.Y)
        targetXY.Y = _zoneXY1.Y;
      else if (targetXY.Y > _zoneXY2.Y)
        targetXY.Y = _zoneXY2.Y;
    }

    private void sub_E456 (Match match) {
      Entity entity = match.EntityBall;
      if (match.EntityHoldingBall != null)
        entity = match.EntityHoldingBall;
      Point targetXY = sub_E7F6 (entity);
      int d3 = sub_E5CC ();
      int type = -1;
      int d0 = -1;
      if ((targetXY.X < 272) || (targetXY.X > 368)) {
        if (targetXY.X < 272) {
          d0 = sub_E60A (targetXY, d3);
          if (d0 <= 352) {
            if (d0 <= 256)
              d0 = 256;
          }
          else
            d0 = 320;
        }
        else if (targetXY.X > 368) {
          d0 = sub_E5F8 (targetXY, d3);
          if (d0 >= 288) {
            if (d0 >= 384)
              d0 = 384;
          }
          else
            d0 = 320;
        }
        type = 2;
      }
      // Else1
      if (type == -1) {
        int cas = -1;
        if (_attributes._initialDir == 0) {
          if (targetXY.Y < 1024)
            type = 1;
          else {
            if (entity._dir == 3)
              cas = 2;
            else if (entity._dir == 5)
              cas = 3;
            else if (entity._dir == 4)
              cas = 1;
          }
        }
        else {
          if (targetXY.Y > 128)
            type = 1;
          else {
            if (entity._dir == 7)
              cas = 3;
            else if (entity._dir == 1)
              cas = 2;
            else if (entity._dir == 0)
              cas = 1;
          }
        }
        if (cas == 1) {
          if ((entity == match.EntityBall) || (entity._pThink == 0xF078))
            type = 1;
        }
        else if ((cas == 2) || (cas == 3)) {
          if (cas == 2) {
            d0 = sub_E60A (targetXY, d3);
            if (d0 > 352) {
              d0 = sub_E5F8 (targetXY, d3);
              if (d0 >= 288)
                type = 3;
              else
                type = 1;
            }
          }
          else if (cas == 3) {
            d0 = sub_E5F8 (targetXY, d3);
            if (d0 < 288) {
              d0 = sub_E60A (targetXY, d3);
              if (d0 <= 352)
                type = 3;
              else
                type = 1;
            }
          }
          if (type == -1) {
            if ((entity == match.EntityBall) || (entity._pThink == 0xF078))
              type = 2;
            else
              type = 3;
          }
        }
        if (type == -1) {
          d0 = sub_E5F8 (targetXY, d3);
          if (d0 < 288) {
            d0 = sub_E60A (targetXY, d3);
            if (d0 > 352)
              type = 1;
            else
              type = 3;
          }
          else {
            d0 = sub_E60A (targetXY, d3);
            if (d0 <= 352)
              type = 1;
            else {
              d0 = sub_E5F8 (targetXY, d3);
              type = 3;
            }
          }
        }
      }

      if (type == -1)
        throw new Exception ();
      if ((type >= 2) && (d0 == -1))
        throw new Exception ();

      if (type == 3)
        d0 = (d0 + targetXY.X) / 2;
      if (type >= 2)
        targetXY.X = d0;
      targetXY.Y = d3;
      sub_E41A (ref targetXY);
      _targetXY = targetXY;
      sub_EB7A (match);
    }

    private int sub_E5CC () {
      if (_attributes._initialDir == 0)
        return (_zoneXY2.Y);
      else
        return (_zoneXY1.Y);
    }

    private int sub_E5E2 () {
      if (_attributes._initialDir == 0)
        return (1120);
      else
        return (32);
    }

    private int sub_E5F8 (Point targetXY, int d3) {
      int d0 = Math.Abs (d3 - targetXY.Y);
      return (targetXY.X - d0);
    }

    private int sub_E60A (Point targetXY, int d3) {
      int d0 = Math.Abs (d3 - targetXY.Y);
      return (targetXY.X + d0);
    }

    private void sub_E61A_GoalUnk (Match match) {
      int type = -1;
      Entity entity = match.EntityHoldingBall;
      Point targetXY = Point.Empty;
      if ((match.EntityHoldingBall == null) ||
          (_attributes._initialDir != match.EntityHoldingBall.GetPlayer._attributes._initialDir)) {
        if (match.EntityHoldingBall == null)
          entity = match.EntityBall;
        if (!Bit6_IsOffscreen) {
          _unkBonusFromSpd = sub_DE88_CalcBonusFromSpd ();
          if ((_unkBonusFromSpd * 8) >= _distanceToBall) {
            targetXY = sub_E7F6 (entity);
            sub_E982 (match, targetXY);
            return;
          }
        }
        if (entity._velocityXY.IsEmpty) {
          if ((_attributes._agr / 2) > Game.myRandom.NextByte ()) {
            targetXY = _terrainXY;
            type = 1;
          }
        }
      }
      // Else0
      if (type == -1) {
        targetXY = sub_E7F6 (entity);
        int d3 = sub_E5E2 ();
        int d0 = -1;
        if ((targetXY.X < 272) || (targetXY.X > 368)) {
          if (targetXY.X < 272) {
            d0 = sub_E60A (targetXY, d3);
            if (d0 <= 368) { // bug?
              if (d0 <= 272)
                d0 = 272;
            }
            else
              d0 = 320;
          }
          else if (targetXY.X > 368) {
            d0 = sub_E5F8 (targetXY, d3);
            if (d0 <= 272) { // bug?
              if (d0 >= 368)
                d0 = 368;
            }
            else
              d0 = 320;
          }
          type = 3;
        }
        // Else1
        if (type == -1) {
          int cas = -1;
          if (_attributes._initialDir == 0) {
            if (targetXY.Y < 1024)
              type = 2;
            else {
              if (entity._dir == 3)
                cas = 2;
              else if (entity._dir == 5)
                cas = 3;
              else if (entity._dir == 4)
                cas = 1;
            }
          }
          else {
            if (targetXY.Y > 128)
              type = 2;
            else {
              if (entity._dir == 7)
                cas = 3;
              else if (entity._dir == 1)
                cas = 2;
              else if (entity._dir == 0)
                cas = 1;
            }
          }
          if (cas == 1) {
            if ((entity == match.EntityBall) || (entity._pThink == 0xF078))
              type = 2;
          }
          else if ((cas == 2) || (cas == 3)) {
            if (cas == 2) {
              d0 = sub_E60A (targetXY, d3);
              if (d0 > 368) {
                d0 = sub_E5F8 (targetXY, d3);
                if (d0 >= 272)
                  type = 4;
                else
                  type = 2;
              }
            }
            else if (cas == 3) {
              d0 = sub_E5F8 (targetXY, d3);
              if (d0 < 272) {
                d0 = sub_E60A (targetXY, d3);
                if (d0 <= 368)
                  type = 4;
                else
                  type = 2;
              }
            }
            if (type == -1) {
              if ((entity == match.EntityBall) || (entity._pThink == 0xF078))
                type = 3;
              else
                type = 4;
            }
          }
          if (type == -1) {
            d0 = sub_E5F8 (targetXY, d3);
            if (d0 < 272) {
              d0 = sub_E60A (targetXY, d3);
              if (d0 > 368)
                type = 2;
              else
                type = 4;
            }
            else {
              d0 = sub_E60A (targetXY, d3);
              if (d0 <= 352)
                type = 2;
              else {
                d0 = sub_E5F8 (targetXY, d3);
                type = 4;
              }
            }
          }
        }

        if (type == -1)
          throw new Exception ();
        if ((type >= 3) && (d0 == -1))
          throw new Exception ();
        if ((type >= 2) && (d3 == -1))
          throw new Exception ();

        if (type == 4)
          d0 = (d0 + targetXY.X) / 2;
        if (type >= 3)
          targetXY.X = (targetXY.X + d0) / 2;
        if (type >= 2)
          targetXY.Y = (targetXY.Y + d3) / 2;
      }
      if (targetXY.IsEmpty)
        throw new Exception ();

      sub_E41A (ref targetXY);
      sub_E438 (ref targetXY);
      _targetXY = targetXY;
      sub_EB7A (match);

    }

    private Point sub_E7F6 (Entity entity) {
      Point xy = entity._terrainXY;
      int d3 = CalcBonusFromInt2 ();
      int d0;
      for (; ; ) {
        d0 = entity._velocityXY.Y;
        d0 <<= d3;
        xy.Y += d0;
        if (d3 == 0)
          break;
        if (((_attributes._initialDir != 0) || (xy.Y <= _zoneXY2.Y))
            &&
            (xy.Y >= _zoneXY1.Y))
          break;
        xy.Y -= d0;
        d3--;
      }
      d0 = entity._velocityXY.X;
      d0 <<= d3;
      xy.X += d0;
      return (xy);
    }

    private bool sub_E854 (Match match) {
      if (match.Bit7_IsClockPaused)
        throw new Exception ();
      if (Bit6_IsOffscreen)
        return (false);
      Player [] players = match.PlayersTeam1;
      if (Bit3_IsTeam1_IsElectrobounced)
        players = match.PlayersTeam2;
      foreach (Player player in players) {
        if (player.Bit4_IsFalling)
          continue;
        if (_distanceToOpposingPlayers [player._index] > 30)
          continue;
        if (player.Bit6_IsOffscreen)
          continue;
        if ((this != match.EntityHoldingBall) &&
            ((_attributes._pos == 0) ||
             (player == match.EntityHoldingBall) ||
             (_attributes._agr > _random))) {
          if ((this == match.PlayerHavingInputTeam1) ||
              (this == match.PlayerHavingInputTeam2))
            sub_EA56 (match, player);
          else
            sub_EAF4 (player);
          return (true);
        }
        else {
          sub_EB38 (match, player);
          return (true);
        }
      }

      {
        Player player = match.PlayerHavingInputTeam1;
        if (!Bit3_IsTeam1_IsElectrobounced)
          player = match.PlayerHavingInputTeam2;
        if (!player.Bit4_IsFalling) {
          if (player != this) {
            int x = player._terrainXY.X + player._originXY.X;
            int y = player._terrainXY.Y + player._originXY.Y;
            Point p1 = new Point (x, y);
            if (UnkInRect (p1, 30) <= 30) {
              Point p2;
              if (CalcVector (p1, out p2) <= 32) {
                sub_EB38 (match, player);
                return (true);
              }
            }
          }
        }
        return (false);
      }
    }

    private void sub_E982 (Match match, Point targetXY) {
      _targetXY = targetXY;
      Bit0_IsBusy = true;
      _unkBonusFromSpd = sub_DE88_CalcBonusFromSpd ();
      DirBits dirBits = GetDirBitsToTargetBetter (targetXY);
      if ((match.EntityHoldingBall != null) ||
          ((_unkBonusFromSpd * 6) < _distanceToBall) ||
          (match.Bit2_IsBallInScoreMultiplier) ||
          (match.EntityBall._spriteIndex <= 2)) {
        if (dirBits.HasNoDirection)
          _dir = _attributes._initialDir;
        else
          _dir = GetDirFromDirBits (dirBits);
        int pOpcodesList, velocity;
        sub_EA62_CalcOpcodesTypeAndVelocityC (match, out pOpcodesList, out velocity);
        sub_F5A8_SetOpcodesAndVelocityFromDir (pOpcodesList, velocity);
      }
      else {
        int velocity = 0;
        if (dirBits.HasDirection) {
          _dir = GetDirFromDirBits (dirBits);
          velocity = 4;
          if (_attributes._spd > Match.SpeedMaxForVelocity4) {
            velocity++;
            if (_attributes._spd > Match.SpeedMaxForVelocity5) {
              velocity++;
              if (_attributes._spd > Match.SpeedMaxForVelocity6)
                velocity++;
            }
          }
        }
        sub_F5A8_SetOpcodesAndVelocityFromDir (0x5EC6, velocity);
        _opcodeIndex = 0;
        Bit1_IsJumping = true;
        _pThink = 0xF290;
      }
    }

    private void sub_EA56 (Match match, Entity entity) {
      Point targetXY = sub_F364 (entity);
      sub_E982 (match, targetXY);
    }

    private void sub_EA62_CalcOpcodesTypeAndVelocityC (Match match, out int pOpcodesList, out int velocity) {
      Bit5_IsTackling_IsBlocking = true;
      _opcodeIndex = 0;
      if ((_attributes._pos == 0) &&
          (match.EntityHoldingBall == null) &&
          (match.EntityBall._velocityXY != Point.Empty) &&
          ((_dir & 3) != 0)) {
        pOpcodesList = 0x5FE6;
        if (_attributes._initialDir != 0)
          pOpcodesList = 0x6006;
        velocity = 8;
        _pThink = 0xF334;
      }
      else {
        pOpcodesList = 0x5E46;
        velocity = 5;
        if (_attributes._spd > Match.SpeedMaxForVelocity4) {
          velocity++;
          if (_attributes._spd > Match.SpeedMaxForVelocity5) {
            velocity++;
            if (_attributes._spd > Match.SpeedMaxForVelocity6)
              velocity++;
          }
        }
        _pThink = 0xF33C;
      }
    }

    private void sub_EAF4 (Player player) {
      DirBits dirBits = GetDirBitsToTargetBetter (player._terrainXY);
      if (dirBits.HasNoDirection)
        _dir = _attributes._initialDir;
      else
        _dir = GetDirFromDirBits (dirBits);
      _opcodeIndex = 0;
      Bit0_IsBusy = true;
      sub_F5A8_SetOpcodesAndVelocityFromDir (0x5E66, 0);
      _pThink = 0xED56;
    }

    private void sub_EB38 (Match match, Player player) {
      DirBits dirBits = GetDirBitsToTargetBetter (player._terrainXY);
      if (dirBits.HasNoDirection)
        _dir = _attributes._initialDir;
      else {
        int dir = GetDirFromDirBits (dirBits);
        _dir = (dir + 4) & 7;
      }
      _opcodeIndex &= 7;
      int pOpcodesList, velocity;
      sub_ECF6_CalcOpcodesTypeAndVelocityA (match, out pOpcodesList, out velocity);
      sub_F5A8_SetOpcodesAndVelocityFromDir (pOpcodesList, velocity);
      _pThink = 0xF344;
    }

    public void sub_EB7A (Match match) {
      DirBits dirBits = GetDirBitsToTargetBetter (_targetXY);
      if (dirBits.HasDirection) {
        int deltaX = Math.Abs (_targetXY.X - _terrainXY.X);
        if (deltaX < 4) {
          _terrainXY.X = _targetXY.X;
          dirBits.Bit2_IsLeft = false;
          dirBits.Bit3_IsRight = false;
        }
        int deltaY = Math.Abs (_targetXY.Y - _terrainXY.Y);
        if (deltaY < 4) {
          _terrainXY.Y = _targetXY.Y;
          dirBits.Bit0_IsUp = false;
          dirBits.Bit1_IsDown = false;
        }
        if (_terrainXY != _targetXY) {
          _dir = GetDirFromDirBits (dirBits);
          int pOpcodesList, velocity;
          sub_ECF6_CalcOpcodesTypeAndVelocityA (match, out pOpcodesList, out velocity);
          sub_F5A8_SetOpcodesAndVelocityFromDir (pOpcodesList, velocity);
          _opcodeIndex &= 7;
          _pThink = 0xEC0C;
          return;
        }
      }
      sub_F212 (match);
    }

    private void sub_EC0C (Match match) {
      int d0 = UnkInRect (_targetXY, 32);
      DirBits dirBits;
      if (d0 <= 32)
        dirBits = sub_F44A (_targetXY);
      else {
        if (_opcodeIndex != 0)
          return;
        dirBits = GetDirBitsToTargetBetter (_targetXY);
      }
      if (dirBits.HasDirection) {
        int deltaX = Math.Abs (_targetXY.X - _terrainXY.X);
        if (deltaX < 4) {
          _terrainXY.X = _targetXY.X;
          dirBits.Bit2_IsLeft = false;
          dirBits.Bit3_IsRight = false;
        }
        int deltaY = Math.Abs (_targetXY.Y - _terrainXY.Y);
        if (deltaY < 4) {
          _terrainXY.Y = _targetXY.Y;
          dirBits.Bit0_IsUp = false;
          dirBits.Bit1_IsDown = false;
        }
        if (_terrainXY != _targetXY) {
          _dir = GetDirFromDirBits (dirBits);
          int pOpcodesList, velocity;
          sub_ECF6_CalcOpcodesTypeAndVelocityA (match, out pOpcodesList, out velocity);
          sub_F5A8_SetOpcodesAndVelocityFromDir (pOpcodesList, velocity);
          return;
        }
      }
      if ((this == match.PlayerHavingInputTeam1) ||
          (this == match.PlayerHavingInputTeam2)) {
        if (_attributes._pos != 0) {
          if (match.EntityHoldingBall != null) {
            if (this != match.EntityHoldingBall) {
              if (!match.Bit7_IsClockPaused) {
                _targetXY = sub_F364 (match.EntityHoldingBall);
                sub_EB7A (match);
                return;
              }
            }
          }
        }
      }
      sub_F212 (match);
    }

    private void sub_ECF6_CalcOpcodesTypeAndVelocityA (Match match, out int pOpcodesList, out int velocity) {
      pOpcodesList = 0x5E86;
      if (_attributes._pos == 0) {
        if (!match.Bit7_IsClockPaused) {
          pOpcodesList = 0x5F86;
          if (_attributes._initialDir != 0)
            pOpcodesList = 0x5FC6;
        }
      }

      velocity = 4;
      if (_attributes._spd > Match.SpeedMaxForVelocity4) {
        velocity++;
        if (_attributes._spd > Match.SpeedMaxForVelocity6)
          velocity++;
      }
      if (this != match.EntityHoldingBall)
        if (velocity != 7)
          velocity++;
    }

    private void sub_ED56_TackleCheckHit (Match match) {
      if (match.Bit7_IsClockPaused)
        return;
      if (Bit6_IsOffscreen)
        return;
      Player [] players;
      if (Bit3_IsTeam1_IsElectrobounced)
        players = match.PlayersTeam2;
      else
        players = match.PlayersTeam1;
      sub_ED92_TackleCheckHit_Helper (match, players);
    }

    private void sub_ED92_TackleCheckHit_Helper (Match match, Player [] players) {
      foreach (Player a5_player in players) {
        if (a5_player.Bit4_IsFalling)
          continue;
        if (_distanceToOpposingPlayers [a5_player._index] > 30)
          continue;
        if (a5_player.Bit6_IsOffscreen)
          continue;
        if (!a5_player.sub_FC24 (match))
          continue;
        _pThink = 0xF2E2;
        if (Game.myRandom.NextByte () > a5_player.sub_EE9C_CalcTackleProbability (match, this))
          return;
        _successfulTackles++;
        a5_player.sub_EEFC_HitByTackle (match, this);
        a5_player._opcodeIndex = 0;
        a5_player.Bit0_IsBusy = true;
        a5_player.Bit4_IsFalling = true;
        if ((a5_player._pThink != 0xF334) &&
            (a5_player._pThink != 0xF33C) &&
            (a5_player._pThink != 0xED56))
          a5_player._pThink = 0xF344;
        int dir = _dir;
        if (Bit4_IsFalling)
          dir = (_dir + 4) & 7;
        a5_player._dir = dir;
        int velocity = 3;
        if (Bit5_IsTackling_IsBlocking)
          velocity = 4;
        a5_player.sub_F5A8_SetOpcodesAndVelocityFromDir (0x5EE6, velocity);
        if (a5_player != match.EntityHoldingBall)
          return;
        if (Bit4_IsFalling) {
          match.EntityHoldingBall = null;
          return;
        }
        else {
          match.EntityHoldingBall = this;
          match.EntityBall._pOpcodes = 0x5588;
          match.EntityBall._opcodeIndex = 0;
          return;
        }
      }
    }

    private int sub_EE9C_CalcTackleProbability (Match match, Player player) {
      int d4 = player._attributes._att;
      int d5 = _attributes._def;
      if (_attributes._pos == 0) {
        d5 = d5 + d5 / 2;
        if (d5 > 255)
          d5 = 255;
      }
      int deltaDir = (_dir - player._dir + 8) & 7;
      d5 -= Match.GetTackleDefMalus (deltaDir);
      if (player.Bit5_IsTackling_IsBlocking)
        d5 -= match._tackleUnk01;
      if (Bit1_IsJumping)
        d5 -= match._tackleUnk02;
      return ((d4 + 256 - d5) / 2);
    }

    public void sub_EEFC_HitByTackle (Match match, Player player) {
      int d3 = player._attributes._pow;
      int d4 = _attributes._sta;
      d3 = ((d3 + 150 - d4) / 16);
      if (d3 == 0)
        d3 = 1;
      _attributes._health -= d3;
      if (_attributes._health < 0)
        _attributes._health = 0;

      d3 /= 2;
      if (d3 == 0)
        d3 = 1;
      ReduceAttribute (ref _attributes._agr, d3);
      ReduceAttribute (ref _attributes._att, d3);
      ReduceAttribute (ref _attributes._def, d3);
      ReduceAttribute (ref _attributes._spd, d3);
      ReduceAttribute (ref _attributes._thr, d3);
      ReduceAttribute (ref _attributes._pow, d3);
      ReduceAttribute (ref _attributes._sta, d3);
      ReduceAttribute (ref _attributes._int, d3);
      sub_FF3A_UnequipEquipment (match);
    }

    private void sub_EF50 (Match match) {
      _targetXY = sub_DEA4_SetInputUnk (match);
      Point p2;
      int distance = CalcVector (_targetXY, out p2);
      if (distance <= (_attributes._thr * 2))
        sub_EFFC (match);
      else
        sub_EFD8 (match);
    }

    private void sub_EF7C (Match match) {
      _targetXY = sub_DEA4_SetInputUnk (match);
      DirBits dirBits = GetDirBitsToTargetBetter (_targetXY);
      if (dirBits.Data == match._dirBitsA.Data) {
        int x = 32;
        if (((_random >> 4) & 1) == 1)
          x = 608;
        _targetXY.X = x;
        int deltaX = Math.Abs (_targetXY.X - _terrainXY.X);
        int y = _terrainXY.Y;
        if (_attributes._initialDir == 0)
          y -= deltaX;
        else
          y += deltaX;
        _targetXY.Y = y;
      }
      sub_EFD8 (match);
    }

    private void sub_EFD8 (Match match) {
      if (Bit3_IsTeam1_IsElectrobounced)
        match._input1.Bit7_IsButton = true;
      else
        match._input2.Bit7_IsButton = true;
      sub_F020 (match);
    }

    private void sub_EFFC (Match match) {
      if (Bit3_IsTeam1_IsElectrobounced)
        match._input1.Bit7_IsButton = false;
      else
        match._input2.Bit7_IsButton = false;
      sub_F020 (match);
    }

    private void sub_F020 (Match match) {
      DirBits dirBits = GetDirBitsToTargetBetter (_targetXY);
      if (dirBits.Data == 0)
        _dir = _attributes._initialDir;
      else
        _dir = GetDirFromDirBits (dirBits);
      _opcodeIndex = 0;
      sub_F5A8_SetOpcodesAndVelocityFromDir (0x5EA6, 0);
      Bit0_IsBusy = true;
      Bit2_IsThrowing = true;
      _pThink = 0xF078;
      match.PlayerThrower = this;
    }

    private void sub_F078 (Match match) {
      if (_opcodeIndex < 4)
        return;
      if (!match.Bit7_IsClockPaused)
        match.EntityHoldingBall = null;
      match.SetBallSpeedFromThr (_attributes._thr);
      Entity entityBall = match.EntityBall;
      entityBall._dir = _dir;
      entityBall._velocityXY = Match.GetVelocityXYFromDir (_dir, 8);
      entityBall._pOpcodes = 0x5528;
      entityBall._opcodeIndex = 0;
      Input inputOld;
      if (Bit3_IsTeam1_IsElectrobounced) {
        match._electrobouncedBallRemainingHits = match.ScoreMultipliers._team1;
        match._electrobouncedBallRemainingHits++;
        Input input = match._input1;
        inputOld = (Input) input.Clone ();
        input.Bit7_IsButton = false;
        if (match.Bit0_IsComputerPlayer1) {
          input.Bit2_IsLeft = false;
          input.Bit3_IsRight = false;
        }
      }
      // Else1
      else {
        match._electrobouncedBallRemainingHits = match.ScoreMultipliers._team2;
        match._electrobouncedBallRemainingHits++;
        Input input = match._input2;
        inputOld = (Input) input.Clone ();
        input.Bit7_IsButton = false;
        if (match.Bit1_IsComputerPlayer2) {
          input.Bit2_IsLeft = false;
          input.Bit3_IsRight = false;
        }
      }
      if (inputOld.Bit7_IsButton)
        entityBall._pOpcodes = 0x54C6;
      if (entityBall._velocityXY.X == 0) {
        if (inputOld.Bit2_IsLeft)
          entityBall._velocityXY.X = -4;
        else if (inputOld.Bit3_IsRight)
          entityBall._velocityXY.X = 4;
      }
      else if (entityBall._velocityXY.Y == 0) {
        if (inputOld.Bit0_IsUp)
          entityBall._velocityXY.Y = -4;
        else if (inputOld.Bit1_IsDown)
          entityBall._velocityXY.Y = 4;
      }
      _pThink = 0xF344;
    }

    private void sub_F1B4 (Match match) {
      DirBits dirBits = GetDirBitsToTargetBetter (match.EntityBall._terrainXY);
      if (dirBits.HasDirection) {
        _dir = GetDirFromDirBits (dirBits);
        int pOpcodesList;
        if (_attributes._pos == 0) {
          if (_attributes._initialDir == 0)
            pOpcodesList = 0x5F26;
          else
            pOpcodesList = 0x5F46;
        }
        else
          pOpcodesList = 0x5F06;
        _pOpcodes = Match.GetOpcodesFromOpcodesList (pOpcodesList, _dir);
        _opcodeIndex = 0;
        Bit0_IsBusy = true;
      }
      _pThink = 0xF344;
    }

    private void sub_F212 (Match match) {
      if (match.Bit7_IsClockPaused) {
        if (_dir != _attributes._initialDir) {
          if (_attributes._initialDir == 0)
            _dir--;
          else
            _dir++;
          _dir = (_dir + 8) % 8;
          _timer = 2;
        }
      }
      int pOpcodesList, velocity;
      sub_F25C_CalcOpcodesTypeAndVelocityB (match, out pOpcodesList, out velocity);
      sub_F5A8_SetOpcodesAndVelocityFromDir (pOpcodesList, velocity);
      _opcodeIndex = 0;
      _pThink = 0xF344;
    }

    private void sub_F25C_CalcOpcodesTypeAndVelocityB (Match match, out int pOpcodesList, out int velocity) {
      pOpcodesList = 0x5E26;
      if (_attributes._pos == 0) {
        if (!match.Bit7_IsClockPaused) {
          _dir = _attributes._initialDir;
          pOpcodesList = 0x5F66;
          if (_attributes._initialDir != 0)
            pOpcodesList = 0x5FA6;
        }
      }
      velocity = 0;
    }

    private void sub_F290 (Match match) {
      if ((_unkBonusFromSpd + 2) > _opcodeIndex)
        return;
      _opcodeIndex = 18;
      match._input1.Bit7_IsButton = false;
      if (this == match.EntityHoldingBall)
        match.EntityBall._opcodeIndex = 18;
      _pThink = 0xF344;
      Bit1_IsJumping = false;
      //todo: sound
    }

    private void sub_F2E2 (Match match) {
      if (((_unkBonusFromSpd + 256 - 1) & 0xFF) > _opcodeIndex)
        return;
      _opcodeIndex = 15;
      if (Bit3_IsTeam1_IsElectrobounced)
        match._input1.Bit7_IsButton = false;
      else
        match._input2.Bit7_IsButton = false;
      _pThink = 0xF344;
      if (match.Bit7_IsClockPaused)
        return;
      //todo: sound
    }

    private void sub_F334 (Match match) {
      sub_F2E2 (match);
      sub_ED56_TackleCheckHit (match);
    }

    private void sub_F33C (Match match) {
      sub_F2E2 (match);
      sub_ED56_TackleCheckHit (match);
    }

    public void sub_F346_CalcBonusFromInt () {
      int index = (_attributes._int - 100) / 10;
      int offset = 0x532 + index;
      _timer = Game.AtariDisk.ReadByte (Game.AtariRamToDisk (offset));
    }

    private Point sub_F364 (Entity a0_entity) {
      int d3 = GetBonusFromInt02 ((_attributes._int - 100) / 10);
      Point targetXY = new Point (a0_entity._terrainXY.X + (a0_entity._velocityXY.X << d3),
                                  a0_entity._terrainXY.Y + (a0_entity._velocityXY.Y << d3));
      sub_F3A2_sub_F3C4 (ref targetXY);
      return (targetXY);
    }

    private void sub_F3A2_sub_F3C4 (ref Point p) {
      if (p.X <= 32)
        p.X = 64 - p.X;
      else if (p.X >= 608)
        p.X = 1216 - p.X;
      if (p.Y <= 32)
        p.Y = 64 - p.Y;
      else if (p.Y >= 1120)
        p.Y = 2240 - p.Y;
    }

    private DirBits sub_F44A (Point p) {
      DirBits dirBits = new DirBits ();
      if (p.X > _terrainXY.X) {
        dirBits.Bit3_IsRight = true;
      }
      else if (p.X < _terrainXY.X) {
        dirBits.Bit2_IsLeft = true;
      }
      if (p.Y > _terrainXY.Y) {
        dirBits.Bit1_IsDown = true;
      }
      else if (p.Y < _terrainXY.Y) {
        dirBits.Bit0_IsUp = true;
      }
      return (dirBits);
    }

    private void sub_F47E (Match match, Input input) {
      int dir = GetDirFromDirBits (input.DirBits);
      int pOpcodesList, velocity;
      sub_F25C_CalcOpcodesTypeAndVelocityB (match, out pOpcodesList, out velocity);
      if (dir < 0)
        _opcodeIndex = 0;
      else {
        if (!sub_FBE4 (match))
          dir ^= 4;
        _dir = dir;
        sub_ECF6_CalcOpcodesTypeAndVelocityA (match, out pOpcodesList, out velocity);
        _opcodeIndex &= 7;
      }
      if (input.Bit7_IsButton) {
        if (this == match.EntityHoldingBall) {
          pOpcodesList = 0x5EA6;
          velocity = 0;
          Bit0_IsBusy = true;
          Bit2_IsThrowing = true;
          _opcodeIndex = 0;
          _pThink = 0xF078;
          match.PlayerThrower = this;
        }
        // Else 1
        else {
          _unkBonusFromSpd = sub_DE88_CalcBonusFromSpd ();
          if (((_unkBonusFromSpd * 6) >= _distanceToBall) &&
              (match.EntityHoldingBall == null) &&
              (!match.Bit2_IsBallInScoreMultiplier) &&
              (match.EntityBall._spriteIndex > 2)) {
            pOpcodesList = 0x5EC6;
            Bit0_IsBusy = true;
            Bit1_IsJumping = true;
            _opcodeIndex = 0;
            _pThink = 0xF290;
          }
          // Else2
          else {
            Bit0_IsBusy = true;
            if (velocity != 0)
              sub_EA62_CalcOpcodesTypeAndVelocityC (match, out pOpcodesList, out velocity);
            else {
              if (Bit3_IsTeam1_IsElectrobounced)
                match._input1.Bit7_IsButton = false;
              else
                match._input2.Bit7_IsButton = false;
              pOpcodesList = 0x5E66;
              _pThink = 0xED56;
            }
          }
        }
      }
      sub_F5A8_SetOpcodesAndVelocityFromDir (pOpcodesList, velocity);
    }

    private void sub_F6B8 () {
      int offset = 0x2B8C + _spriteIndex * 2;
      _originXY = new Point (Game.AtariDisk.ReadByteSigned (Game.AtariRamToDisk (offset)),
                             Game.AtariDisk.ReadByteSigned (Game.AtariRamToDisk (offset + 1)));
    }

    public void sub_F6DC_InjuredInitMedic (Match match) {
      if (match._playerInjured != null)
        if (match._playerInjured.Bit0_IsBusy)
          return;
      if (Bit3_IsTeam1_IsElectrobounced)
        match.PlayerHavingInputTeam1 = this;
      else
        match.PlayerHavingInputTeam2 = this;
      _pOpcodes = 0x5B72;
      _opcodeIndex = 0;
      _terrainXY.X &= 0xFFFE;
      _terrainXY.Y &= 0xFFFE;
      if (_terrainXY.X > 592)
        _terrainXY.X = 592;
      if (_terrainXY.X < 48)
        _terrainXY.X = 48;
      if (_terrainXY.Y < 48)
        _terrainXY.Y = 48;
      if (_terrainXY.Y > 1104)
        _terrainXY.Y = 1104;
      match._playerInjured = this;
      match.EntityHoldingBall = match.EntityTerrainCenter;
      match.EntityTerrainCenter.Bit0_IsBusy = true;
      match.EntityTerrainCenter._terrainXY = _terrainXY;
      match._unk10Injured = _terrainXY;
      match._unk11Injured = _terrainXY;
      match._injuryState = 0;
      Entity medic1 = match.EntityMedic1;
      Entity medic2 = match.EntityMedic2;
      medic1._terrainXY.Y = _terrainXY.Y;
      medic2._terrainXY.Y = _terrainXY.Y;
      if (_terrainXY.X <= 320) {
        medic2._terrainXY.X = 592;
        medic1._terrainXY.X = 560;
        match._unk10Injured.X = -26;
        match._unk11Injured.X += 32;
      }
      else {
        medic2._terrainXY.X = 48;
        medic1._terrainXY.X = 80;
        match._unk10Injured.X = 26;
        match._unk11Injured.X -= 32;
      }
      if (_terrainXY.Y <= 576)
        match._unk10Injured.Y += 32;
      else
        match._unk10Injured.Y -= 32;
      match.Bit7_IsClockPaused = true;
      match.Bit3_IsPlayerInjured = true;
      medic1.Bit7_IsInvisible = false;
      medic2.Bit7_IsInvisible = false;
      match.IncScoreWithBonusPoints (!Bit3_IsTeam1_IsElectrobounced, 10);
    }

    private void sub_FB6C (Match match) {
      if (match.Bit7_IsClockPaused)
        return;
      if (Bit4_IsFalling)
        return;
      if (_attributes._pos == 0) {
        if (_velocityXY.Y > 0) {
          if ((_terrainXY.Y + _velocityXY.Y) > _zoneXY2.Y)
            _terrainXY.Y -= _velocityXY.Y;
        }
        else if (_velocityXY.Y < 0) {
          if ((_terrainXY.Y + _velocityXY.Y) < _zoneXY1.Y)
            _terrainXY.Y -= _velocityXY.Y;
        }
      }
      // freeze team
      if (match._token._heldSpriteIndex != 55)
        return;
      if (match._token._holdingPlayer._attributes._initialDir == _attributes._initialDir)
        return;
      _terrainXY.X -= _velocityXY.X;
      _terrainXY.Y -= _velocityXY.Y;
    }

    private bool sub_FBE4 (Match match) {
      if (match._token._heldSpriteIndex != 56)
        return (true);
      int initialDir = match._token._holdingPlayer._attributes._initialDir;
      initialDir ^= 4;
      return (initialDir != _attributes._initialDir);
    }

    public bool sub_FC24 (Match match) {
      if (match._token._heldSpriteIndex != 64)
        return (true);
      int initialDir = match._token._holdingPlayer._attributes._initialDir;
      return (initialDir != _attributes._initialDir);
    }

    private void sub_FF3A_UnequipEquipment (Match match) {
      if (_equipmentSpriteIndex == 0)
        return;
      Entity equipment = match.EntityItemEquipment;
      equipment.Bit7_IsInvisible = false;
      equipment._terrainXY.X = (_terrainXY.X & 0xFE0) + 16;
      equipment._terrainXY.Y = (_terrainXY.Y & 0xFE0) + 16;
      match._equipmentOnFloorSpriteIndex = _equipmentSpriteIndex;
      int attributeIndex = GetAttributeIndexFromEquipment (_equipmentSpriteIndex - 80) - 72;
      if (attributeIndex == 0)
        UnequipEquipment_Helper (ref _attributes._agr, ref _agrTemp, this);
      else if (attributeIndex == 1)
        UnequipEquipment_Helper (ref _attributes._att, ref _attTemp, this);
      else if (attributeIndex == 2)
        UnequipEquipment_Helper (ref _attributes._def, ref _defTemp, this);
      else if (attributeIndex == 3)
        UnequipEquipment_Helper (ref _attributes._spd, ref _spdTemp, this);
      else if (attributeIndex == 4)
        UnequipEquipment_Helper (ref _attributes._thr, ref _thrTemp, this);
      else if (attributeIndex == 5)
        UnequipEquipment_Helper (ref _attributes._pow, ref _powTemp, this);
      else if (attributeIndex == 6)
        UnequipEquipment_Helper (ref _attributes._sta, ref _staTemp, this);
      else if (attributeIndex == 7)
        UnequipEquipment_Helper (ref _attributes._int, ref _intTemp, this);
      else
        throw new Exception ();
      _equipmentSpriteIndex = 0;
      _equipmentBackupAttribute = 0;
      if (match._equipmentNbPickupRemaining == 0)
        match.EntityItemEquipment.InitItemEquipment (match);
    }

    private static void UnequipEquipment_Helper (ref int attribute, ref int attributeTemp, Player player) {
      if (attributeTemp != 0) {
        attribute = player._equipmentBackupAttributeTemp;
        attributeTemp = player._equipmentBackupAttribute;
      }
      else
        attribute = player._equipmentBackupAttribute;
    }

    public void BackupAttributesAgrEtcAndSetValue (int value) {
      _agrTemp = _attributes._agr;
      _attributes._agr = value;
      _attTemp = _attributes._att;
      _attributes._att = value;
      _defTemp = _attributes._def;
      _attributes._def = value;
      _spdTemp = _attributes._spd;
      _attributes._spd = value;
      _thrTemp = _attributes._thr;
      _attributes._thr = value;
      _powTemp = _attributes._pow;
      _attributes._pow = value;
      _staTemp = _attributes._sta;
      _attributes._sta = value;
      _intTemp = _attributes._int;
      _attributes._int = value;
    }

    public void RestoreAttributesAgrEtc () {
      _attributes._agr = _agrTemp;
      _agrTemp = 0;
      _attributes._att = _attTemp;
      _attTemp = 0;
      _attributes._def = _defTemp;
      _defTemp = 0;
      _attributes._spd = _spdTemp;
      _spdTemp = 0;
      _attributes._thr = _thrTemp;
      _thrTemp = 0;
      _attributes._pow = _powTemp;
      _powTemp = 0;
      _attributes._sta = _staTemp;
      _staTemp = 0;
      _attributes._int = _intTemp;
      _intTemp = 0;
    }

    public void BackupAttributeSpdAndSet100 () {
      _spdTemp = _attributes._spd;
      _attributes._spd = 100;
    }

    public void RestoreAttributeSpd () {
      _attributes._spd = _spdTemp;
      _spdTemp = 0;
    }

    private void ReduceAttribute (ref int attribute, int d3) {
      attribute -= d3;
      if (attribute <= 100)
        attribute = 100;
    }

    public int CalcBonusFromInt2 () {
      int index = (_attributes._int - 100) / 10;
      int offset = 0x542 + index;
      return (Game.AtariDisk.ReadByte (Game.AtariRamToDisk (offset)));
    }

    public Attributes GetPersonAttributes (Match match) {
      return (match.GetPersonAttributes (_personAttributesIndex, _personAttributesType));
    }

    public static Point [] _presentationXY = null;
    public static void InitPresentationXY (Match match) {
      if (_presentationXY != null)
        return;
      _presentationXY = new Point [18];
      int i = 0;
      foreach (Player player in match.PlayersTeam1)
        _presentationXY [i++] = player._terrainXY;
      foreach (Player player in match.PlayersTeam2)
        _presentationXY [i++] = player._terrainXY;
    }

    public static Point GetPresentationXY (int presentationIndex) {
      return (_presentationXY [presentationIndex]);
      //int offset = 0x405E + presentationIndex * 4;
      //return (new Point (Game.Disk.ReadWord (Game.RamToDisk (offset)),
      //                   Game.Disk.ReadWord (Game.RamToDisk (offset + 2))));
    }

    public static Point GetBallDeltasXY (int spriteIndex) {
      int offset = 0x2AA2 + spriteIndex * 2;
      return (new Point (Game.AtariDisk.ReadByteSigned (Game.AtariRamToDisk (offset)),
                         Game.AtariDisk.ReadByteSigned (Game.AtariRamToDisk (offset + 1))));
    }

    public static int GetBonusFromInt02 (int index) {
      int offset = 0x542 + index;
      return (Game.AtariDisk.ReadByte (Game.AtariRamToDisk (offset)));
    }

    public static new int Size {
      get {
        return (0x72);
      }
    }

    public new void Dump () {
      Console.WriteLine ("Player {1} ({2}): {0:X8}", Game.AtariDiskToRam (_offset), _index, _index + ((Bit3_IsTeam1_IsElectrobounced) ? 0 : 9));
      base.Dump ();
      Console.Write ("{0:X4} ", _distanceToBall);
      Console.Write ("{0:X4} ", _targetXY.X);
      Console.Write ("{0:X4} ", _targetXY.Y);
      Console.Write ("{0:X4}  <- Player", _launchXY.X);
      Console.WriteLine ();
      Console.Write ("{0:X4} ", _launchXY.Y);
      Console.Write ("{0:X4} ", _zoneCenterXY.X);
      Console.Write ("{0:X4} ", _zoneCenterXY.Y);
      Console.Write ("{0:X4} ", _zoneXY1.X);
      Console.WriteLine ();
      Console.Write ("{0:X4} ", _zoneXY2.X);
      Console.Write ("{0:X4} ", _zoneXY1.Y);
      Console.Write ("{0:X4} ", _zoneXY2.Y);
      Console.Write ("{0:X2} ", _timeInPossession);
      Console.Write ("{0:X2} ", _goalsScored);
      Console.WriteLine ();
      Console.Write ("{0:X2} ", _successfulTackles);
      Console.Write ("{0:X2} ", _goalsSaved);
      Console.Write ("{0:X2} ", _substitutions);
      Console.Write ("{0:X2} ", _random);
      Console.Write ("{0:X2} ", _unkBonusFromSpd);
      _attributes.Dump ();
      Console.Write ("{0:X2} ", _agrTemp);
      Console.Write ("{0:X2} ", _attTemp);
      Console.Write ("{0:X2} ", _defTemp);
      Console.Write ("{0:X2} ", _spdTemp);
      Console.Write ("{0:X2} ", _thrTemp);
      Console.Write ("{0:X2} ", _powTemp);
      Console.Write ("{0:X2} ", _staTemp);
      Console.Write ("{0:X2} ", _intTemp);
      Console.WriteLine ();
      Console.Write ("{0:X2} ", _equipmentSpriteIndex);
      Console.Write ("{0:X2} ", _equipmentBackupAttribute);
      Console.Write ("{0:X2} ", _equipmentBackupAttributeTemp);
      Console.Write ("{0:X2} ", _field_5B_b);
      //Console.Write ("{0:X8} ", 0x40A6 + _personIndex * Attributes.Size);
      Console.Write ("{0:X8} ", _pPersonAttributes);
      Console.WriteLine ();
      for (int i = 0; i < 9; i++)
        Console.Write ("{0:X4} ", _distanceToOpposingPlayers [i]);
      Console.WriteLine ();
    }

    public static void DumpAllPlayers () {
      foreach (Player player in Match.Instance.PlayersTeam1)
        player.Dump ();
      foreach (Player player in Match.Instance.PlayersTeam2)
        player.Dump ();
    }
  }
}
